from enum import Enum
from sanic import Blueprint
from sanic.response import json, text
from sanic.request import Request

from databases import rank_db, problem_db
from cookie import COOKIE_KEY
from hrd_core import MoveList, Problem
from utils import CheckCookie, CheckJson, CheckArgument
from my_logging import GetLogger

from .interface_definition import UPLOAD_MOVES_CHECKING, UPLOAD_PROBLEM_CHECKING

class ProblemIdCheckResult(Enum):
    CHECK_PASS = 0
    PROBLEM_ID_INVALID = 1
    NOT_EXISTS = 2

data_bp = Blueprint("data_blueprint", url_prefix="/data")
logger = GetLogger('data_blueprint')


def checkProblemIdInRequest(request : Request) -> tuple[int, ProblemIdCheckResult]:
    try:
        problem_id = int(request.args['problem_id'][0])
    except:
        return problem_id, ProblemIdCheckResult.PROBLEM_ID_INVALID

    if not problem_db.IsProblemExists(problem_id):
        return problem_id, ProblemIdCheckResult.NOT_EXISTS

    return problem_id, ProblemIdCheckResult.CHECK_PASS


@data_bp.route('/problem', methods=['GET'])
@CheckCookie()
@CheckArgument('problem_id', text('Rubbish', status = 400))
async def handleGetTable(request : Request):
    problem_id, check_result = checkProblemIdInRequest(request)

    if check_result == ProblemIdCheckResult.NOT_EXISTS:
        return text('Rubbish', status = 404)
    if check_result == ProblemIdCheckResult.PROBLEM_ID_INVALID:
        return text('Rubbish', status = 400)

    return json(problem_db.GetProblem(problem_id))


@data_bp.route('/rank_list', methods=['GET'])
@CheckCookie()
@CheckArgument('problem_id', text('Rubbish', status = 400))
async def handleGetRankList(request : Request):
    problem_id, check_result = checkProblemIdInRequest(request)

    if check_result == ProblemIdCheckResult.NOT_EXISTS:
        return text('Rubbish', status = 404)
    if check_result == ProblemIdCheckResult.PROBLEM_ID_INVALID:
        return text('Rubbish', status = 400)

    rank_list = rank_db.GetRankList(problem_id)

    return json(
        {
            'rank_list' : [
                {
                    'name': record.username, 'step': record.step
                } for record in rank_list
            ]
        }
    )


@data_bp.route('/upload_moves', methods=['POST'])
@CheckCookie()
@CheckJson(UPLOAD_MOVES_CHECKING)
async def handleUploadMoves(request : Request):
    cookie_in_request = request.cookies.get(COOKIE_KEY)

    problem_id = request.json['problem_id']
    username = request.json['username']
    move_dict_list = request.json['move_list']

    if not problem_db.IsProblemExists(problem_id):
        logger.info(f'Problem [id={problem_id}] not exists.')
        return text('I got rubbish.', status = 400)

    problem = Problem(problem_db.GetProblem(problem_id))
    move_list = MoveList(move_dict_list)

    if not move_list.Verify():
        logger.info(f'Move_list verifying not pass.')
        return text('I got rubbish.', status = 400)

    is_updating_own_record = False
    if rank_db.IsUsernameInRankList(problem_id, username):
        user_cookie = rank_db.GetUserCookie(problem_id, username)
        if user_cookie != cookie_in_request:
            logger.debug(f'Cookie in_db = [{user_cookie}], in_request = [{cookie_in_request}]')
            return text('Username already ranked.', status = 400)
        else:
            is_updating_own_record = True

    if not problem.IsMovesSolvesProblem(move_list):
        return text('Moves not accepted.', status = 400)

    step = move_list.CalculateCompressedSteps()
    if is_updating_own_record:
        rank_db.UpdateRank(cookie_in_request, problem_id, step)
    else:
        rank_db.InsertRank(username, problem_id, step, cookie_in_request)

    return text('Accepted.')


@data_bp.route('/upload_problem', methods=['POST'])
@CheckCookie()
@CheckJson(UPLOAD_PROBLEM_CHECKING)
async def handleUploadProblem(request : Request):
    designer = request.json['designer']
    description = request.json['description']
    problem_dict = {'cars' : request.json['cars']}

    p = Problem(problem_dict)
    if not p.IsSolvable():
        return text('Problem not accepted.', status = 400)

    problem_db.InsertDiyProblem(
        problem_dict,
        designer,
        description
    )

    return text('I got the problem.')


@data_bp.route('/user_id', methods=['GET'])
@CheckCookie()
async def handleGetUserId(request : Request):
    cookie_in_request = request.cookies.get(COOKIE_KEY)

    return text(cookie_in_request)


@data_bp.route('/sync', methods=['POST'])
@CheckCookie()
@CheckArgument('user_id')
async def handleRequestSynchronization(request : Request):
    cookie_in_request = request.cookies.get(COOKIE_KEY)
    user_id = request.args['user_id'][0]

    search_result = rank_db.SearchCookieInRankList(user_id)
    if not search_result:
        return text('No such user.', status = 404)
    if len(search_result) > 1:
        return text('Not specific enough.', status = 400)
    requesting_cookie = search_result[0]

    if cookie_in_request == requesting_cookie:
        return text('Already be that man.', status = 400)

    mainly_use_name = rank_db.MergeRecord(cookie_in_request, requesting_cookie)

    response = text(mainly_use_name)
    response.add_cookie(
        COOKIE_KEY, requesting_cookie,
        secure=False,
        httponly=True
    )

    return response
